package cn.mujiankeji.mbrowser.功能.下载.引擎

import cn.mujiankeji.mbrowser.功能.下载.模型.下载任务
import cn.mujiankeji.mbrowser.功能.下载.模型.下载任务分块状态
import cn.mujiankeji.mbrowser.功能.下载.模型.下载状态
import cn.mujiankeji.mbrowser.功能.下载.模型.下载引擎类型
import cn.mujiankeji.mbrowser.工具.mlog
import io.ktor.client.HttpClient
import io.ktor.client.request.head
import io.ktor.client.request.header
import io.ktor.client.request.prepareGet
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.bodyAsChannel
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.isEmpty
import io.ktor.utils.io.core.readBytes
import io.ktor.utils.io.readRemaining
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.io.File
import java.io.RandomAccessFile
import java.util.Locale
import java.util.concurrent.atomic.AtomicLong

class 默认下载引擎(
    httpClient: HttpClient // HttpClient 通过构造函数注入
) : Ktor下载引擎基础(httpClient) {

    private val TAG = "下载引擎"

    companion object {
        private const val 缓存区大小 = 8192 // 8KB buffer  每次请求的大小
        private const val 分块最小尺寸 = 5 * 1024 * 1024 // 5MB  即大于5MB就使用分块下载，方便
        private const val 最大并发数 = 8 // 或称最大线程数
    }

    override suspend fun 执行下载(
        任务: 下载任务,
        任务状态流: MutableStateFlow<下载任务>
    ) {
        var 下载任务 = 任务.copy()
        val 保存文件 = File(下载任务.目标文件夹, 下载任务.文件名)
        var 文件总太小 = 下载任务.文件大小
        var 支持范围请求 = false // 即分块请求，是断点续下、多线程下载的必要的因素

        mlog(TAG, "执行下载任务 for ${下载任务.文件名}, URL: ${下载任务.url}")

        保存文件.parentFile?.mkdirs()

        下载任务 = 下载任务.copy(状态 = 下载状态.正在下载)
        任务状态流.value = 下载任务

        try {
            // 创建基础请求
            val headResponse: HttpResponse = httpClient.head(下载任务.url) {
                下载任务.请求头.forEach { (key, value) -> header(key, value) }
                if (下载任务.UserAgent != null) header(HttpHeaders.UserAgent, 下载任务.UserAgent)
            }

            // 获取文件返回的请求头信息
            if (headResponse.status == HttpStatusCode.OK || headResponse.status == HttpStatusCode.PartialContent) {
                val serverContentLength = headResponse.headers[HttpHeaders.ContentLength]?.toLongOrNull()
                val acceptRanges = headResponse.headers[HttpHeaders.AcceptRanges]

                if (serverContentLength != null && serverContentLength > 0) {
                    文件总太小 = serverContentLength
                } else if (下载任务.文件大小 > 0) {
                    文件总太小 = 下载任务.文件大小
                }
                支持范围请求 = "bytes" == acceptRanges?.trim()?.lowercase(Locale.getDefault())
            } else {
                支持范围请求 = false
            }
        }
        catch (e: Exception) {
            支持范围请求 = false
            // 加载请求头失败，不能确定文件是否支持范围请求。
            mlog(TAG, "由于HEAD错误，在没有确认文件信息的情况下继续。")
        }

        // 更新任务项目的文件大小信息
        if (文件总太小 > 0 && 下载任务.文件大小 != 文件总太小) {
            下载任务 = 下载任务.copy(文件大小 = 文件总太小)
            任务状态流.value = 下载任务
        }

        //  如果文件已存在，请确保目标文件在分块下载时是可查找和可写的
        //  我们正在尝试对部分下载的文件进行分块下载。
        //  对于分块下载，我们通常希望控制所有部分，所以如果存在部分文件，
        //  我们可能需要一个策略（例如，验证现有块或重新启动）。目前，我们假设从新的或兼容的部分块开始。
        val 使用分块下载 = 支持范围请求 &&
                文件总太小 > 分块最小尺寸 &&
                下载任务.引擎类型 != 下载引擎类型.KTOR_M3U8

        if (使用分块下载) {
            执行分块下载(下载任务, 保存文件, 任务状态流, 文件总太小)
        } else {
            完整下载(下载任务, 保存文件, 任务状态流, 文件总太小)
        }
    }


    /**
     * 分块下载 -- 支持断点续链的文件多线程分块下载
     */
    private suspend fun 执行分块下载(
        任务: 下载任务,
        targetFile: File,
        taskFlow: MutableStateFlow<下载任务>,
        文件大小: Long
    ) {
        val 分块大小 = 256 * 1024
        var task = 任务.copy(状态 = 下载状态.正在下载, 文件大小 = 文件大小)
            .also {
                if (it.分块大小 == 0 || it.分块数量 == 0) {
                    it.分块大小 = 分块大小
                    it.分块数量 = ((文件大小 + it.分块大小 - 1) / it.分块大小).toInt()
                }
                if (it.分块状态 == null) it.分块状态 = Array(it.分块数量){下载任务分块状态.未开始}

            }

        val mutex = Mutex()
        val 分块状态 = task.分块状态!!
        taskFlow.value = task

        // 用于控制所有协程是否应该停止工作
        var 停止 = false

        // 预分配文件
        if (!targetFile.exists() || targetFile.length() < 文件大小) {
            RandomAccessFile(targetFile, "rw").use { raf ->
                raf.setLength(文件大小)
            }
        }

        val 已下载的大小 = AtomicLong((分块状态.count { it == 下载任务分块状态.完成 } * 分块大小
            .toLong()))

        val job = SupervisorJob()

        fun 下载失败(错误信息 : String){
            taskFlow.value = task.copy(状态 = 下载状态.失败, 错误信息 = 错误信息)
            job.cancel()
        }
        fun 下载块完成(){
            task = task.copy(已下载大小 = 已下载的大小.get(), 分块状态 = 分块状态)
            taskFlow.value = task
            if (已下载的大小.get() == 文件大小 || 分块状态.count { it == 下载任务分块状态.完成 } == 分块状态.size){
                taskFlow.value = task.copy(状态 = 下载状态.已完成)
            }
        }
        withContext(Dispatchers.IO + job){
            repeat(最大并发数) { workerId ->
                launch(Dispatchers.IO) {
                    while (isActive && !停止) {
                        // 寻找新的快
                        val 块索引 = synchronized(分块状态) {
                            if (停止) return@synchronized -1
                            分块状态.indexOfFirst {
                                it == 下载任务分块状态.未开始 || it == 下载任务分块状态.失败
                            }
                        }
                        if (块索引 == -1) break

                        // 标记处理中
                        分块状态[块索引] = 下载任务分块状态.下载中
                        val start = (块索引 * task.分块大小.toLong())
                        val end = minOf(start + task.分块大小 - 1, 文件大小 - 1)
                        var 重试次数 = 0
                        val 最大重试次数 = 5
                        var success = false

                        while (重试次数 < 最大重试次数 && !success && isActive && !停止) {
                            try {
                                val statement = httpClient.prepareGet(task.url) {
                                    task.请求头.forEach { (key, value) -> header(key, value) }
                                    if (task.UserAgent != null) header(HttpHeaders.UserAgent, task.UserAgent)
                                    header(HttpHeaders.Range, "bytes=$start-$end")
                                }.execute()

                                if (statement.status == HttpStatusCode.PartialContent || (statement.status == HttpStatusCode.OK && start == 0L)) {
                                    RandomAccessFile(targetFile, "rw").use { raf ->
                                        raf.seek(start)
                                        val channel: ByteReadChannel = statement.bodyAsChannel()
                                        var bytesRead = 0L
                                        while (!channel.isClosedForRead && isActive && !停止) {
                                            currentCoroutineContext().ensureActive()
                                            val packet = channel.readRemaining(缓存区大小.toLong())
                                            if (packet.isEmpty) {
                                                continue
                                            }
                                            val bytes = packet.readBytes()
                                            raf.write(bytes)

                                            bytesRead += bytes.size
                                            val newTotal = 已下载的大小.addAndGet(bytes.size.toLong())

                                            // 只在每8KB的倍数时更新UI，减少频繁更新
                                            if (bytesRead % (缓存区大小 * 4) == 0L) {
                                                task = task.copy(已下载大小 = newTotal)
                                                taskFlow.value = task
                                            }
                                        }
                                        val expected = end - start + 1
                                        if (bytesRead == expected) {
                                            success = true
                                        }
                                        else {
                                            已下载的大小.addAndGet(-bytesRead)
                                        }
                                    }
                                } else {
                                    mlog(TAG, "Worker $workerId - Chunk $块索引 HTTP错误: ${statement.status}")
                                }
                            } catch (e: Exception) {
                                if (e is kotlinx.coroutines.CancellationException) {
                                    mlog(TAG, "Worker $workerId - Chunk $块索引 被取消: ${e.message}")
                                    throw e // 重新抛出取消异常，让协程正常取消
                                } else {
                                    mlog(TAG, "Worker $workerId - Chunk $块索引 异常: ${e.message}", e)
                                }
                            }
                            if (!success) {
                                重试次数++
                                if (isActive && 重试次数 < 最大重试次数 && !停止) {
                                    mlog(TAG, "Worker $workerId - Chunk $块索引 第${重试次数}次重试, 等待${重试次数}秒")
                                    kotlinx.coroutines.delay(1000L * 重试次数)
                                }
                            }
                        }

                        if (!success) {
                            分块状态[块索引] = 下载任务分块状态.失败 // 允许后续协程重试

                            // 如果是关键块失败（如第一块），或者失败次数过多，则停止所有下载
                            val 失败块数 = synchronized(分块状态) { 分块状态.size - 分块状态.count { it == 下载任务分块状态.失败 } }
                            val 成功块数 = synchronized(分块状态) { 分块状态.count { it == 下载任务分块状态.完成 } }
                            val failureRate = 失败块数.toFloat() / task.分块数量
                            if (块索引 == 0 || failureRate > 0.3) { // 如果是第一块失败或失败率超过30%
                                下载失败("--")
                                break
                            }
                        }
                        else{
                            分块状态[块索引] = 下载任务分块状态.完成
                            下载块完成()
                        }
                    }
                }
                delay(50)
            }
        }
    }


    /**
     * 完整下载，文件较小或不支持断点续下的任务
     */
    private suspend fun 完整下载(
        任务: 下载任务,
        targetFile: File,
        任务状态流: MutableStateFlow<下载任务>,
        文件大小: Long // Total size as determined by HEAD or initially known
    ) {
        mlog("完整下载不分块")
        var currentTask = 任务.copy()
        var randomAccessFile: RandomAccessFile? = null
        var totalBytesWritten = if (targetFile.exists()) targetFile.length() else 0L

        if (文件大小 > 0 && currentTask.文件大小 != 文件大小) {
             currentTask = currentTask.copy(文件大小 = 文件大小)
        }
        else if (文件大小 <= 0 && currentTask.文件大小 > 0) {
            // No change needed.
        }

        if (currentTask.文件大小 in 1..totalBytesWritten) {
            mlog(TAG, "File ${currentTask.文件名} already downloaded. Path: ${targetFile.absolutePath}, Size: $totalBytesWritten / ${currentTask.文件大小}")
            currentTask = currentTask.copy(状态 = 下载状态.已完成, 已下载大小 = totalBytesWritten)
            任务状态流.value = currentTask
            return
        }
        
        currentTask = currentTask.copy(已下载大小 = totalBytesWritten, 状态 = 下载状态.正在下载)
        任务状态流.value = currentTask
        mlog(TAG, "准备单连接下载: ${currentTask.文件名}, 从 $totalBytesWritten 字节开始, 任务总大小: ${currentTask.文件大小}")

        try {
            randomAccessFile = RandomAccessFile(targetFile, "rw")
            randomAccessFile.seek(totalBytesWritten)

            val statement = httpClient.prepareGet(currentTask.url) {
                currentTask.请求头.forEach { (key, value) -> header(key, value) }
                if (currentTask.UserAgent != null) header(HttpHeaders.UserAgent, currentTask.UserAgent)
                if (totalBytesWritten > 0) {
                    header(HttpHeaders.Range, "bytes=$totalBytesWritten-")
                    mlog(TAG, "设置Range头 for single download: bytes=$totalBytesWritten-")
                }
            }.execute()

            mlog(TAG, "HTTP响应状态 (single download): ${statement.status} for ${currentTask.文件名}")

            val serverContentLength = statement.headers[HttpHeaders.ContentLength]?.toLongOrNull()
            var responseTotalSize = currentTask.文件大小

            when (statement.status) {
                HttpStatusCode.OK -> {
                    if (totalBytesWritten > 0) {
                        mlog(TAG, "Server responded with OK, but expected PartialContent (resuming). Restarting download for ${currentTask.文件名}.")
                        totalBytesWritten = 0L
                        randomAccessFile.seek(0L)
                        randomAccessFile.setLength(0L)
                        currentTask = currentTask.copy(已下载大小 = 0L)
                    }
                    if (serverContentLength != null && serverContentLength > 0) {
                        responseTotalSize = serverContentLength
                    } else if (totalBytesWritten == 0L && responseTotalSize <= 0) {
                        mlog(TAG, "从头开始下载 ${currentTask.文件名}. Total size unknown.")
                    }
                }
                HttpStatusCode.PartialContent -> {
                    val contentRange = statement.headers[HttpHeaders.ContentRange]
                    val totalFromServerInRange = contentRange?.substringAfterLast('/', "")?.toLongOrNull()
                    if (totalFromServerInRange != null && totalFromServerInRange > 0) {
                        responseTotalSize = totalFromServerInRange
                    }
                    mlog(TAG, "服务器支持断点续传 (single download): ${currentTask.文件名}, Content-Range: $contentRange, 解析总大小: $responseTotalSize")
                }
                HttpStatusCode.RequestedRangeNotSatisfiable -> {
                    if (currentTask.文件大小 in 1..totalBytesWritten) {
                        currentTask = currentTask.copy(状态 = 下载状态.已完成, 已下载大小 = totalBytesWritten)
                        任务状态流.value = currentTask
                        randomAccessFile.close()
                        return
                    }
                    val errorMsg = "HTTP Error: ${statement.status} (Range Not Satisfiable, but file not complete)"
                    mlog(TAG, errorMsg)
                    currentTask = currentTask.copy(状态 = 下载状态.失败, 错误信息 = errorMsg)
                    任务状态流.value = currentTask
                    randomAccessFile.close()
                    return
                }
                else -> {
                    val errorMsg = "HTTP Error: ${statement.status}"
                    currentTask = currentTask.copy(状态 = 下载状态.失败, 错误信息 = errorMsg)
                    任务状态流.value = currentTask
                    randomAccessFile.close()
                    return
                }
            }

            if (responseTotalSize > 0 && currentTask.文件大小 != responseTotalSize) {
                currentTask = currentTask.copy(文件大小 = responseTotalSize)
                任务状态流.value = currentTask
            }

            val channel: ByteReadChannel = statement.bodyAsChannel()
            while (!channel.isClosedForRead) {
                currentCoroutineContext().ensureActive()
                val packet = channel.readRemaining(缓存区大小.toLong())
                if (packet.isEmpty) continue
                val bytes = packet.readBytes()
                randomAccessFile.write(bytes)
                totalBytesWritten += bytes.size
                currentTask = currentTask.copy(已下载大小 = totalBytesWritten)
                任务状态流.value = currentTask
            }

            if (currentTask.文件大小 > 0) {
                when {
                    totalBytesWritten == currentTask.文件大小 -> currentTask = currentTask.copy(状态 = 下载状态.已完成)
                    totalBytesWritten < currentTask.文件大小 -> currentTask = currentTask.copy(状态 = 下载状态.失败, 错误信息 = "下载数据不完整")
                    else -> currentTask = currentTask.copy(状态 = 下载状态.已完成, 文件大小 = totalBytesWritten)
                }
            } else {
                currentTask = currentTask.copy(状态 = 下载状态.已完成, 已下载大小 = totalBytesWritten, 文件大小 = totalBytesWritten)
            }
        } catch (e: kotlinx.coroutines.CancellationException) {
            mlog(TAG, "下载被取消/暂停 (single): ${currentTask.文件名} - ${e.message}")
            if (currentTask.状态 == 下载状态.正在下载) {
                 currentTask = currentTask.copy(状态 = 下载状态.已暂停, 错误信息 = "下载被中断: ${e.message}")
            }
        } catch (e: Exception) {
            mlog(TAG, "下载异常 (single): ${currentTask.文件名}, ${e.message}", e)
            currentTask = currentTask.copy(状态 = 下载状态.失败, 错误信息 = e.localizedMessage ?: "未知下载错误")
        } finally {
            try {
                randomAccessFile?.close()
            } catch (ioe: Exception) {
                mlog(TAG, "关闭文件失败 (single): ${currentTask.文件名}", ioe)
            }
            任务状态流.value = currentTask
            mlog(TAG, "单连接下载结束: ${currentTask.文件名}, 状态: ${currentTask.状态}, ${currentTask.已下载大小}/${currentTask.文件大小}")
        }
    }
} 